/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/core/SkBlurMaskFilterImpl.h"

#include "include/core/SkBlurTypes.h"
#include "include/core/SkFlattenable.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/private/base/SkAlign.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkTemplates.h"
#include "src/core/SkBlitter_A8.h"
#include "src/core/SkBlurMask.h"
#include "src/core/SkCachedData.h"
#include "src/core/SkDrawBase.h"
#include "src/core/SkMask.h"
#include "src/core/SkMaskCache.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkRasterClip.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkResourceCache.h"
#include "src/core/SkWriteBuffer.h"

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <native_drawing/drawing_mask_filter.h>

SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle style, bool respectCTM)
    : fSigma(sigma)
    , fBlurStyle(style)
    , fRespectCTM(respectCTM) {
    SkASSERT(fSigma > 0);
    SkASSERT((unsigned)style <= kLastEnum_SkBlurStyle);
    static_assert((int)SkBlurStyle::kNormal_SkBlurStyle == OH_Drawing_BlurType::NORMAL, "BlurStyle not equal!");
    static_assert((int)SkBlurStyle::kSolid_SkBlurStyle == OH_Drawing_BlurType::SOLID, "BlurStyle not equal!");
    static_assert((int)SkBlurStyle::kOuter_SkBlurStyle == OH_Drawing_BlurType::OUTER, "BlurStyle not equal!");
    static_assert((int)SkBlurStyle::kInner_SkBlurStyle == OH_Drawing_BlurType::INNER, "BlurStyle not equal!");
    fMaskFilter = OH_Drawing_MaskFilterCreateBlur((OH_Drawing_BlurType)fBlurStyle, fSigma, respectCTM);
    DrawingHash hasher(HASH_UNIQUE_ID);
    hasher.push(fBlurStyle);
    hasher.push(fSigma);
    hasher.push(respectCTM);
    fHash = hasher.digest();
}

SkBlurMaskFilterImpl::~SkBlurMaskFilterImpl() {
    if (fMaskFilter) {
        OH_Drawing_MaskFilterDestroy(fMaskFilter);
    }
    fMaskFilter = nullptr;
}

SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
    return SkMask::kA8_Format;
}

bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const {
    if (this->ignoreXform()) {
        return false;
    }

    if (rec) {
        rec->fSigma = fSigma;
        rec->fStyle = fBlurStyle;
    }
    return true;
}

SkScalar SkBlurMaskFilterImpl::computeXformedSigma(const SkMatrix& ctm) const {
    constexpr SkScalar kMaxBlurSigma = SkIntToScalar(128);
    SkScalar xformedSigma = this->ignoreXform() ? fSigma : ctm.mapRadius(fSigma);
    return std::min(xformedSigma, kMaxBlurSigma);
}

bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
                                      const SkMatrix& matrix,
                                      SkIPoint* margin) const {
    SkScalar sigma = this->computeXformedSigma(matrix);
    return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, margin);
}

bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
                                          const SkMatrix& matrix,
                                          SkIPoint* margin, SkMask::CreateMode createMode) const {
    SkScalar sigma = computeXformedSigma(matrix);

    return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode);
}

bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r,
                                          const SkMatrix& matrix,
                                          SkIPoint* margin, SkMask::CreateMode createMode) const {
    SkScalar sigma = computeXformedSigma(matrix);

    return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode);
}

static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMask* mask) {
    RENDER_UNIMPLEMENTED;
    return false;
//     SkASSERT(mask != nullptr);
//
//     mask->fBounds = bounds.roundOut();
//     mask->fRowBytes = SkAlign4(mask->fBounds.width());
//     mask->fFormat = SkMask::kA8_Format;
//     const size_t size = mask->computeImageSize();
//     mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc);
//     if (nullptr == mask->fImage) {
//         return false;
//     }
//     return true;
}

template <typename Proc> bool draw_into_mask(SkMask* mask, const SkRect& bounds, Proc proc) {
    RENDER_UNIMPLEMENTED;
    return false;
//     if (!prepare_to_draw_into_mask(bounds, mask)) {
//         return false;
//     }
//
//     const int dx = mask->fBounds.fLeft;
//     const int dy = mask->fBounds.fTop;
//     SkRasterClip rclip(mask->fBounds);
//     rclip.setRect(mask->fBounds.makeOffset(-dx, -dy));
//
//     SkASSERT(mask->fFormat == SkMask::kA8_Format);
//     auto info = SkImageInfo::MakeA8(mask->fBounds.width(), mask->fBounds.height());
//     auto pm = SkPixmap(info, mask->fImage, mask->fRowBytes);
//
//     SkMatrix ctm = SkMatrix::Translate(-SkIntToScalar(dx), -SkIntToScalar(dy));
//
//     SkMatrixProvider matrixProvider(ctm);
//
//     SkDrawBase draw;
//     draw.fBlitterChooser = SkA8Blitter_Choose;
//     draw.fMatrixProvider = &matrixProvider;
//     draw.fDst            = pm;
//     draw.fRC             = &rclip;
//
//     SkPaint paint;
//     paint.setAntiAlias(true);
//
//     proc(draw, paint);
//     return true;
}

static bool draw_rects_into_mask(const SkRect rects[], int count, SkMask* mask) {
    RENDER_UNIMPLEMENTED;
    return false;
//     return draw_into_mask(mask, rects[0], [&](SkDrawBase& draw, const SkPaint& paint) {
//         if (1 == count) {
//             draw.drawRect(rects[0], paint);
//         } else {
//             // todo: do I need a fast way to do this?
//             SkPath path = SkPathBuilder().addRect(rects[0])
//                                          .addRect(rects[1])
//                                          .setFillType(SkPathFillType::kEvenOdd)
//                                          .detach();
//             draw.drawPath(path, paint);
//         }
//     });
}

static bool draw_rrect_into_mask(const SkRRect rrect, SkMask* mask) {
    RENDER_UNIMPLEMENTED;
    return false;
//     return draw_into_mask(mask, rrect.rect(), [&](SkDrawBase& draw, const SkPaint& paint) {
//         draw.drawRRect(rrect, paint);
//     });
}

static bool rect_exceeds(const SkRect& r, SkScalar v) {
    return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v ||
           r.width() > v || r.height() > v;
}

static SkCachedData* copy_mask_to_cacheddata(SkMask* mask) {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     const size_t size = mask->computeTotalImageSize();
//     SkCachedData* data = SkResourceCache::NewCachedData(size);
//     if (data) {
//         memcpy(data->writable_data(), mask->fImage, size);
//         SkMask::FreeImage(mask->fImage);
//         mask->fImage = (uint8_t*)data->data();
//     }
//     return data;
}

static SkCachedData* find_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style,
                                       const SkRRect& rrect) {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     return SkMaskCache::FindAndRef(sigma, style, rrect, mask);
}

static SkCachedData* add_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style,
                                      const SkRRect& rrect) {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     SkCachedData* cache = copy_mask_to_cacheddata(mask);
//     if (cache) {
//         SkMaskCache::Add(sigma, style, rrect, *mask, cache);
//     }
//     return cache;
}

static SkCachedData* find_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style,
                                       const SkRect rects[], int count) {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     return SkMaskCache::FindAndRef(sigma, style, rects, count, mask);
}

static SkCachedData* add_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style,
                                      const SkRect rects[], int count) {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     SkCachedData* cache = copy_mask_to_cacheddata(mask);
//     if (cache) {
//         SkMaskCache::Add(sigma, style, rects, count, *mask, cache);
//     }
//     return cache;
}

static const bool c_analyticBlurRRect{true};

SkMaskFilterBase::FilterReturn
SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix,
                                        const SkIRect& clipBounds,
                                        NinePatch* patch) const {
    RENDER_UNIMPLEMENTED;
    return kUnimplemented_FilterReturn;
//     SkASSERT(patch != nullptr);
//     switch (rrect.getType()) {
//         case SkRRect::kEmpty_Type:
//             // Nothing to draw.
//             return kFalse_FilterReturn;
//
//         case SkRRect::kRect_Type:
//             // We should have caught this earlier.
//             SkASSERT(false);
//             [[fallthrough]];
//         case SkRRect::kOval_Type:
//             // The nine patch special case does not handle ovals, and we
//             // already have code for rectangles.
//             return kUnimplemented_FilterReturn;
//
//         // These three can take advantage of this fast path.
//         case SkRRect::kSimple_Type:
//         case SkRRect::kNinePatch_Type:
//         case SkRRect::kComplex_Type:
//             break;
//     }
//
//     // TODO: report correct metrics for innerstyle, where we do not grow the
//     // total bounds, but we do need an inset the size of our blur-radius
//     if (kInner_SkBlurStyle == fBlurStyle) {
//         return kUnimplemented_FilterReturn;
//     }
//
//     // TODO: take clipBounds into account to limit our coordinates up front
//     // for now, just skip too-large src rects (to take the old code path).
//     if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) {
//         return kUnimplemented_FilterReturn;
//     }
//
//     SkIPoint margin;
//     SkMask  srcM, dstM;
//     srcM.fBounds = rrect.rect().roundOut();
//     srcM.fFormat = SkMask::kA8_Format;
//     srcM.fRowBytes = 0;
//
//     bool filterResult = false;
//     if (c_analyticBlurRRect) {
//         // special case for fast round rect blur
//         // don't actually do the blur the first time, just compute the correct size
//         filterResult = this->filterRRectMask(&dstM, rrect, matrix, &margin,
//                                             SkMask::kJustComputeBounds_CreateMode);
//     }
//
//     if (!filterResult) {
//         filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
//     }
//
//     if (!filterResult) {
//         return kFalse_FilterReturn;
//     }
//
//     // Now figure out the appropriate width and height of the smaller round rectangle
//     // to stretch. It will take into account the larger radius per side as well as double
//     // the margin, to account for inner and outer blur.
//     const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner);
//     const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner);
//     const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner);
//     const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner);
//
//     const SkScalar leftUnstretched = std::max(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX);
//     const SkScalar rightUnstretched = std::max(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX);
//
//     // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover
//     // any fractional space on either side plus 1 for the part to stretch.
//     const SkScalar stretchSize = SkIntToScalar(3);
//
//     const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize;
//     if (totalSmallWidth >= rrect.rect().width()) {
//         // There is no valid piece to stretch.
//         return kUnimplemented_FilterReturn;
//     }
//
//     const SkScalar topUnstretched = std::max(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY);
//     const SkScalar bottomUnstretched = std::max(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY);
//
//     const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize;
//     if (totalSmallHeight >= rrect.rect().height()) {
//         // There is no valid piece to stretch.
//         return kUnimplemented_FilterReturn;
//     }
//
//     SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight);
//
//     SkRRect smallRR;
//     SkVector radii[4];
//     radii[SkRRect::kUpperLeft_Corner] = UL;
//     radii[SkRRect::kUpperRight_Corner] = UR;
//     radii[SkRRect::kLowerRight_Corner] = LR;
//     radii[SkRRect::kLowerLeft_Corner] = LL;
//     smallRR.setRectRadii(smallR, radii);
//
//     const SkScalar sigma = this->computeXformedSigma(matrix);
//     SkCachedData* cache = find_cached_rrect(&patch->fMask, sigma, fBlurStyle, smallRR);
//     if (!cache) {
//         bool analyticBlurWorked = false;
//         if (c_analyticBlurRRect) {
//             analyticBlurWorked =
//                 this->filterRRectMask(&patch->fMask, smallRR, matrix, &margin,
//                                       SkMask::kComputeBoundsAndRenderImage_CreateMode);
//         }
//
//         if (!analyticBlurWorked) {
//             if (!draw_rrect_into_mask(smallRR, &srcM)) {
//                 return kFalse_FilterReturn;
//             }
//
//             SkAutoMaskFreeImage amf(srcM.fImage);
//
//             if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
//                 return kFalse_FilterReturn;
//             }
//         }
//         cache = add_cached_rrect(&patch->fMask, sigma, fBlurStyle, smallRR);
//     }
//
//     patch->fMask.fBounds.offsetTo(0, 0);
//     patch->fOuterRect = dstM.fBounds;
//     patch->fCenter.fX = SkScalarCeilToInt(leftUnstretched) + 1;
//     patch->fCenter.fY = SkScalarCeilToInt(topUnstretched) + 1;
//     SkASSERT(nullptr == patch->fCache);
//     patch->fCache = cache;  // transfer ownership to patch
//     return kTrue_FilterReturn;
}

// Use the faster analytic blur approach for ninepatch rects
static const bool c_analyticBlurNinepatch{true};

SkMaskFilterBase::FilterReturn
SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
                                        const SkMatrix& matrix,
                                        const SkIRect& clipBounds,
                                        NinePatch* patch) const {
    RENDER_UNIMPLEMENTED;
    return kUnimplemented_FilterReturn;
//     if (count < 1 || count > 2) {
//         return kUnimplemented_FilterReturn;
//     }
//
//     // TODO: report correct metrics for innerstyle, where we do not grow the
//     // total bounds, but we do need an inset the size of our blur-radius
//     if (kInner_SkBlurStyle == fBlurStyle || kOuter_SkBlurStyle == fBlurStyle) {
//         return kUnimplemented_FilterReturn;
//     }
//
//     // TODO: take clipBounds into account to limit our coordinates up front
//     // for now, just skip too-large src rects (to take the old code path).
//     if (rect_exceeds(rects[0], SkIntToScalar(32767))) {
//         return kUnimplemented_FilterReturn;
//     }
//
//     SkIPoint margin;
//     SkMask  srcM, dstM;
//     srcM.fBounds = rects[0].roundOut();
//     srcM.fFormat = SkMask::kA8_Format;
//     srcM.fRowBytes = 0;
//
//     bool filterResult = false;
//     if (count == 1 && c_analyticBlurNinepatch) {
//         // special case for fast rect blur
//         // don't actually do the blur the first time, just compute the correct size
//         filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin,
//                                             SkMask::kJustComputeBounds_CreateMode);
//     } else {
//         filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
//     }
//
//     if (!filterResult) {
//         return kFalse_FilterReturn;
//     }
//
//     /*
//      *  smallR is the smallest version of 'rect' that will still guarantee that
//      *  we get the same blur results on all edges, plus 1 center row/col that is
//      *  representative of the extendible/stretchable edges of the ninepatch.
//      *  Since our actual edge may be fractional we inset 1 more to be sure we
//      *  don't miss any interior blur.
//      *  x is an added pixel of blur, and { and } are the (fractional) edge
//      *  pixels from the original rect.
//      *
//      *   x x { x x .... x x } x x
//      *
//      *  Thus, in this case, we inset by a total of 5 (on each side) beginning
//      *  with our outer-rect (dstM.fBounds)
//      */
//     SkRect smallR[2];
//     SkIPoint center;
//
//     // +2 is from +1 for each edge (to account for possible fractional edges
//     int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
//     int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
//     SkIRect innerIR;
//
//     if (1 == count) {
//         innerIR = srcM.fBounds;
//         center.set(smallW, smallH);
//     } else {
//         SkASSERT(2 == count);
//         rects[1].roundIn(&innerIR);
//         center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
//                    smallH + (innerIR.top() - srcM.fBounds.top()));
//     }
//
//     // +1 so we get a clean, stretchable, center row/col
//     smallW += 1;
//     smallH += 1;
//
//     // we want the inset amounts to be integral, so we don't change any
//     // fractional phase on the fRight or fBottom of our smallR.
//     const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
//     const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
//     if (dx < 0 || dy < 0) {
//         // we're too small, relative to our blur, to break into nine-patch,
//         // so we ask to have our normal filterMask() be called.
//         return kUnimplemented_FilterReturn;
//     }
//
//     smallR[0].setLTRB(rects[0].left(),       rects[0].top(),
//                       rects[0].right() - dx, rects[0].bottom() - dy);
//     if (smallR[0].width() < 2 || smallR[0].height() < 2) {
//         return kUnimplemented_FilterReturn;
//     }
//     if (2 == count) {
//         smallR[1].setLTRB(rects[1].left(), rects[1].top(),
//                           rects[1].right() - dx, rects[1].bottom() - dy);
//         SkASSERT(!smallR[1].isEmpty());
//     }
//
//     const SkScalar sigma = this->computeXformedSigma(matrix);
//     SkCachedData* cache = find_cached_rects(&patch->fMask, sigma, fBlurStyle, smallR, count);
//     if (!cache) {
//         if (count > 1 || !c_analyticBlurNinepatch) {
//             if (!draw_rects_into_mask(smallR, count, &srcM)) {
//                 return kFalse_FilterReturn;
//             }
//
//             SkAutoMaskFreeImage amf(srcM.fImage);
//
//             if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
//                 return kFalse_FilterReturn;
//             }
//         } else {
//             if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin,
//                                       SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
//                 return kFalse_FilterReturn;
//             }
//         }
//         cache = add_cached_rects(&patch->fMask, sigma, fBlurStyle, smallR, count);
//     }
//     patch->fMask.fBounds.offsetTo(0, 0);
//     patch->fOuterRect = dstM.fBounds;
//     patch->fCenter = center;
//     SkASSERT(nullptr == patch->fCache);
//     patch->fCache = cache;  // transfer ownership to patch
//     return kTrue_FilterReturn;
}

void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src,
                                             SkRect* dst) const {
    // TODO: if we're doing kInner blur, should we return a different outset?
    //       i.e. pad == 0 ?

    SkScalar pad = 3.0f * fSigma;

    dst->setLTRB(src.fLeft  - pad, src.fTop    - pad,
                 src.fRight + pad, src.fBottom + pad);
}

sk_sp<SkFlattenable> SkBlurMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
    const SkScalar sigma = buffer.readScalar();
    SkBlurStyle style = buffer.read32LE(kLastEnum_SkBlurStyle);

    uint32_t flags = buffer.read32LE(0x3);  // historically we only recorded 2 bits
    bool respectCTM = !(flags & 1); // historically we stored ignoreCTM in low bit

    return SkMaskFilter::MakeBlur((SkBlurStyle)style, sigma, respectCTM);
}

void SkBlurMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
    buffer.writeScalar(fSigma);
    buffer.writeUInt(fBlurStyle);
    buffer.writeUInt(!fRespectCTM); // historically we recorded ignoreCTM
}

void sk_register_blur_maskfilter_createproc() { SK_REGISTER_FLATTENABLE(SkBlurMaskFilterImpl); }

sk_sp<SkMaskFilter> SkMaskFilter::MakeBlur(SkBlurStyle style, SkScalar sigma, bool respectCTM) {
    if (SkScalarIsFinite(sigma) && sigma > 0) {
        return sk_sp<SkMaskFilter>(new SkBlurMaskFilterImpl(sigma, style, respectCTM));
    }
    return nullptr;
}
